/*
 * rdesktop.cpp
 *
 *  Created on: Oct 12, 2011
 *      Author: welcome
 */

#include <rdesktop.h>
#include <stdarg.h>
#include <string.h>
#include <KD/kd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/times.h>
#include <ssl.h>
#include <fnmatch.h>
#include <ctype.h>
#include <tcp.h>
#include <rdp.h>

#define	EOS	'\0'

char g_title[64] = "";
char g_keymapname[PATH_MAX] = "";
unsigned int g_keylayout = 0x409;
int g_keyboard_type = 0x4;
int g_keyboard_subtype = 0x0;
int g_keyboard_functionkeys = 0xc;
int g_sizeopt = 0;
int g_width = 320;
int g_height = 480;
int g_xpos = 0;
int g_ypos = 0;
int g_pos = 0;

char keymapname[16];
char *g_username;
char g_hostname[16];
RD_BOOL g_bitmap_cache = True;
int g_server_bpp = 16;
RD_BOOL packet_encryption = True;
uint8 g_client_random[SEC_RANDOM_SIZE];
int g_server_depth = 16;
int g_win_button_size = 0; /* If zero, disable single app mode */
RD_BOOL g_bitmap_compression = True;
RD_BOOL g_sendmotion = True;
RD_BOOL g_bitmap_cache_persist_enable = False;
RD_BOOL g_bitmap_cache_precache = True;
RD_BOOL g_encryption = True;
RD_BOOL g_packet_encryption = True;
RD_BOOL g_desktop_save = True; /* desktop save order */
RD_BOOL g_polygon_ellipse_orders = True; /* polygon / ellipse orders */
RD_BOOL g_fullscreen = True;
RD_BOOL g_grab_keyboard = True;
RD_BOOL g_hide_decorations = False;
RD_BOOL g_use_rdp5 = True;
RD_BOOL g_rdpclip = True;
RD_BOOL g_console_session = False;
RD_BOOL g_numlock_sync = False;
RD_BOOL g_lspci_enabled = False;
RD_BOOL g_owncolmap = False;
RD_BOOL g_ownbackstore = True; /* We can't rely on external BackingStore */
RD_BOOL g_seamless_rdp = False;
RD_BOOL g_user_quit = False;
uint32 g_embed_wnd;
uint32 g_rdp5_performanceflags = RDP5_DISABLE_NOTHING;

RD_BOOL g_has_reconnect_random = False;
RD_BOOL g_pending_resize = False;
uint32 g_reconnect_logonid = 0;
char g_reconnect_random[16];

RD_BOOL g_redirect = False;
char g_redirect_server[64];
char g_redirect_domain[16];
char g_redirect_password[64];
char *g_redirect_username;
char g_redirect_cookie[128];
uint32 g_redirect_flags = 0;

void unimpl(const char *format, ...) {
	va_list ap;

	fprintf(stderr, "NOT IMPLEMENTED: ");

	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
}
void error(const char *format, ...) {
	va_list ap;

	fprintf(stderr, "ERROR: ");

	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
}

int load_licence(unsigned char **data) {
	char *home, *path;
	struct stat st;
	int fd, length;

	home = getenv("HOME");

	if (home == NULL)
		return -1;

	path = (char *) kdMalloc(strlen(home) + strlen(g_hostname)
			+ sizeof("/.rdesktop/licence."));
	sprintf(path, "%s/.rdesktop/licence.%s", home, g_hostname);

	fd = open(path, O_RDONLY);
	if (fd == -1)
		return -1;

	if (fstat(fd, &st))
		return -1;

	*data = (uint8 *) kdMalloc(st.st_size);
	length = read(fd, *data, st.st_size);
	close(fd);
	kdFree(path);
	return length;
}

void save_licence(unsigned char *data, int length) {
	char *home, *path, *tmppath;
	int fd;

	home = getenv("HOME");
	if (home == NULL)
		return;

	path = (char *) kdMalloc(strlen(home) + strlen(g_hostname)
			+ sizeof("/.rdesktop/licence."));

	sprintf(path, "%s/.rdesktop", home);
	if ((mkdir(path, 0700) == -1) && errno != EEXIST) {
		perror(path);
		return;
	}

	/* write licence to licence.hostname.new, then atomically rename to licence.hostname */

	sprintf(path, "%s/.rdesktop/licence.%s", home, g_hostname);
	tmppath = (char *) kdMalloc(strlen(path) + sizeof(".new"));
	strcpy(tmppath, path);
	strcat(tmppath, ".new");

	fd = open(tmppath, O_WRONLY | O_CREAT | O_TRUNC, 0600);
	if (fd == -1) {
		perror(tmppath);
		return;
	}

	if (write(fd, data, length) != length) {
		perror(tmppath);
		unlink(tmppath);
	} else if (rename(tmppath, path) == -1) {
		perror(path);
		unlink(tmppath);
	}

	close(fd);
	kdFree(tmppath);
	kdFree(path);
}

/* Generate a 32-byte random for the secure transport code. */
void generate_random(uint8 * random) {
	struct stat st;
	struct tms tmsbuf;
	SSL_MD5 md5;
	uint32 *r;
	int fd, n;

	/* If we have a kernel random device, try that first */
	if (((fd = open("/dev/urandom", O_RDONLY)) != -1) || ((fd = open(
			"/dev/random", O_RDONLY)) != -1)) {
		n = read(fd, random, 32);
		close(fd);
		if (n == 32)
			return;
	}
	/* Otherwise use whatever entropy we can gather - ideas welcome. */
	r = (uint32 *) random;
	r[0] = (getpid()) | (getuid() << 16);
	r[1] = (getuid()) | (getgid() << 16);
	r[2] = times(&tmsbuf); /* system uptime (clocks) */
	gettimeofday((struct timeval *) &r[3], NULL); /* sec and usec */
	stat("/tmp", &st);
	r[5] = st.st_atime;
	r[6] = st.st_mtime;
	r[7] = st.st_ctime;

	/* Hash both halves with MD5 to obscure possible patterns */
	ssl_md5_init(&md5);
	ssl_md5_update(&md5, random, 16);
	ssl_md5_final(&md5, random);
	ssl_md5_update(&md5, random + 16, 16);
	ssl_md5_final(&md5, random + 16);
}
void warning(const char *format, ...) {
	va_list ap;

	fprintf(stderr, "WARNING: ");

	va_start(ap, format);
	vfprintf(stderr, format, ap);
	va_end(ap);
}
int rd_lseek_file(int fd, int offset) {
	return lseek(fd, offset, SEEK_SET);
}

/* open a file in the .rdesktop directory */
int rd_open_file(char *filename) {
	char *home;
	char fn[256];
	int fd;

	home = getenv("HOME");
	if (home == NULL)
		return -1;
	sprintf(fn, "%s/.rdesktop/%s", home, filename);
	fd = open(fn, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
	if (fd == -1)
		perror(fn);
	return fd;
}

/* close file */
void rd_close_file(int fd) {
	close(fd);
}

/* read from file*/
int rd_read_file(int fd, void *ptr, int len) {
	return read(fd, ptr, len);
}

/* write to file */
int rd_write_file(int fd, void *ptr, int len) {
	return write(fd, ptr, len);
}
/* do a write lock on a file */
RD_BOOL rd_lock_file(int fd, int start, int len) {
	struct flock lock;

	lock.l_type = F_WRLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = start;
	lock.l_len = len;
	if (fcntl(fd, F_SETLK, &lock) == -1)
		return False;
	return True;
}
RD_BOOL rd_pstcache_mkdir(void) {
	char *home;
	char bmpcache_dir[256];

	home = getenv("HOME");

	if (home == NULL)
		return False;

	sprintf(bmpcache_dir, "%s/%s", home, ".rdesktop");

	if ((mkdir(bmpcache_dir, S_IRWXU) == -1) && errno != EEXIST) {
		perror(bmpcache_dir);
		return False;
	}

	sprintf(bmpcache_dir, "%s/%s", home, ".rdesktop/cache");

	if ((mkdir(bmpcache_dir, S_IRWXU) == -1) && errno != EEXIST) {
		perror(bmpcache_dir);
		return False;
	}

	return True;
}
char *
next_arg(char *src, char needle) {
	char *nextval;
	char *p;
	char *mvp = 0;

	/* EOS */
	if (*src == (char) 0x00)
		return 0;

	p = src;
	/*  skip escaped needles */
	while ((nextval = strchr(p, needle))) {
		mvp = nextval - 1;
		/* found backslashed needle */
		if (*mvp == '\\' && (mvp > src)) {
			/* move string one to the left */
			while (*(mvp + 1) != (char) 0x00) {
				*mvp = *(mvp + 1);
				mvp++;
			}
			*mvp = (char) 0x00;
			p = nextval;
		} else {
			p = nextval + 1;
			break;
		}

	}

	/* more args available */
	if (nextval) {
		*nextval = (char) 0x00;
		return ++nextval;
	}

	/* no more args after this, jump to EOS */
	nextval = src + strlen(src);
	return nextval;
}
static const char *
rangematch(const char *pattern, char test, int flags) {
	int negate, ok;
	char c, c2;
	if ((negate = (*pattern == '!' || *pattern == '^')))
		++pattern;

	if (flags & FNM_CASEFOLD)
		test = tolower((unsigned char) test);

	for (ok = 0; (c = *pattern++) != ']';) {
		if (c == '\\' && !(flags & FNM_NOESCAPE))
			c = *pattern++;
		if (c == EOS)
			return (NULL);

		if (flags & FNM_CASEFOLD)
			c = tolower((unsigned char) c);

		if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && c2 != ']') {
			pattern += 2;
			if (c2 == '\\' && !(flags & FNM_NOESCAPE))
				c2 = *pattern++;
			if (c2 == EOS)
				return (NULL);

			if (flags & FNM_CASEFOLD)
				c2 = tolower((unsigned char) c2);

			if ((unsigned char) c <= (unsigned char) test
					&& (unsigned char) test <= (unsigned char) c2)
				ok = 1;
		} else if (c == test)
			ok = 1;
	}
	return (ok == negate ? NULL : pattern);
}

int fnmatch(const char *pattern, const char *string, int flags) {
	const char *stringstart;
	char c, test;

	for (stringstart = string;;)
		switch (c = *pattern++) {
		case EOS:
			if ((flags & FNM_LEADING_DIR) && *string == '/')
				return (0);
			return (*string == EOS ? 0 : FNM_NOMATCH);
		case '?':
			if (*string == EOS)
				return (FNM_NOMATCH);
			if (*string == '/' && (flags & FNM_PATHNAME))
				return (FNM_NOMATCH);
			if (*string == '.' && (flags & FNM_PERIOD) && (string
					== stringstart || ((flags & FNM_PATHNAME) && *(string - 1)
					== '/')))
				return (FNM_NOMATCH);
			++string;
			break;
		case '*':
			c = *pattern;
			/* Collapse multiple stars. */
			while (c == '*')
				c = *++pattern;

			if (*string == '.' && (flags & FNM_PERIOD) && (string
					== stringstart || ((flags & FNM_PATHNAME) && *(string - 1)
					== '/')))
				return (FNM_NOMATCH);

			/* Optimize for pattern with * at end or before /. */
			if (c == EOS)
				if (flags & FNM_PATHNAME)
					return ((flags & FNM_LEADING_DIR) || strchr(string, '/')
							== NULL ? 0 : FNM_NOMATCH);
				else
					return (0);
			else if (c == '/' && flags & FNM_PATHNAME) {
				if ((string = strchr(string, '/')) == NULL)
					return (FNM_NOMATCH);
				break;
			}

			/* General case, use recursion. */
			while ((test = *string) != EOS) {
				if (!fnmatch(pattern, string, flags & ~FNM_PERIOD))
					return (0);
				if (test == '/' && flags & FNM_PATHNAME)
					break;
				++string;
			}
			return (FNM_NOMATCH);
		case '[':
			if (*string == EOS)
				return (FNM_NOMATCH);
			if (*string == '/' && flags & FNM_PATHNAME)
				return (FNM_NOMATCH);
			if ((pattern = rangematch(pattern, *string, flags)) == NULL)
				return (FNM_NOMATCH);
			++string;
			break;
		case '\\':
			if (!(flags & FNM_NOESCAPE)) {
				if ((c = *pattern++) == EOS) {
					c = '\\';
					--pattern;
				}
			}
			/* FALLTHROUGH */
		default:
			if (c == *string)
				;
			else if ((flags & FNM_CASEFOLD) && (tolower((unsigned char) c)
					== tolower((unsigned char) *string)))
				;
			/*else if ((flags & FNM_PREFIX_DIRS) && *string == EOS &&
			 ((c == '/' && string != stringstart) ||
			 (string == stringstart+1 && *stringstart == '/')))
			 return (0);*/
			else
				return (FNM_NOMATCH);
			string++;
			break;
		}
	/* NOTREACHED */
}
#define LTOA_BUFSIZE (sizeof(long) * 8 + 1)

char * l_to_a(long N, int base) {
	static char ret[LTOA_BUFSIZE];

	char *head = ret, buf[LTOA_BUFSIZE], *tail = buf + sizeof(buf);

	register int divrem;

	if (base < 36 || 2 > base)
		base = 10;

	if (N < 0) {
		*head++ = '-';
		N = -N;
	}

	tail = buf + sizeof(buf);
	*--tail = 0;

	do {
		divrem = N % base;
		*--tail = (divrem <= 9) ? divrem + '0' : divrem + 'a' - 10;
		N /= base;
	} while (N);

	strcpy(head, tail);
	return ret;
}
void toupper_str(char *p) {
	while (*p) {
		if ((*p >= 'a') && (*p <= 'z'))
			*p = toupper((int) *p);
		p++;
	}
}
void parse_server_and_port(char *server) {
	char *p;
#ifdef IPv6
	int addr_colons;
#endif

#ifdef IPv6
	p = server;
	addr_colons = 0;
	while (*p)
	if (*p++ == ':')
	addr_colons++;
	if (addr_colons >= 2)
	{
		/* numeric IPv6 style address format - [1:2:3::4]:port */
		p = strchr(server, ']');
		if (*server == '[' && p != NULL)
		{
			if (*(p + 1) == ':' && *(p + 2) != '\0')
			g_tcp_port_rdp = strtol(p + 2, NULL, 10);
			/* remove the port number and brackets from the address */
			*p = '\0';
			strncpy(server, server + 1, strlen(server));
		}
	}
	else
	{
		/* dns name or IPv4 style address format - server.example.com:port or 1.2.3.4:port */
		p = strchr(server, ':');
		if (p != NULL)
		{
			g_tcp_port_rdp = strtol(p + 1, NULL, 10);
			*p = 0;
		}
	}
#else /* no IPv6 support */
	p = strchr(server, ':');
	if (p != NULL) {
		g_tcp_port_rdp = strtol(p + 1, NULL, 10);
		*p = 0;
	}
#endif /* IPv6 */

}
void rdesktop_reset_state(void) {
	rdp_reset_state();
#ifdef WITH_SCARD
	scard_reset_state();
#endif
#ifdef WITH_RDPSND
	rdpsnd_reset_state();
#endif
}
void print_disconnect_reason(uint16 reason) {
	char *text;

	switch (reason) {
	case exDiscReasonNoInfo:
		text = "No information available";
		break;

	case exDiscReasonAPIInitiatedDisconnect:
		text = "Server initiated disconnect";
		break;

	case exDiscReasonAPIInitiatedLogoff:
		text = "Server initiated logoff";
		break;

	case exDiscReasonServerIdleTimeout:
		text = "Server idle timeout reached";
		break;

	case exDiscReasonServerLogonTimeout:
		text = "Server logon timeout reached";
		break;

	case exDiscReasonReplacedByOtherConnection:
		text = "The session was replaced";
		break;

	case exDiscReasonOutOfMemory:
		text = "The server is out of memory";
		break;

	case exDiscReasonServerDeniedConnection:
		text = "The server denied the connection";
		break;

	case exDiscReasonServerDeniedConnectionFips:
		text = "The server denied the connection for security reason";
		break;

	case exDiscReasonLicenseInternal:
		text = "Internal licensing error";
		break;

	case exDiscReasonLicenseNoLicenseServer:
		text = "No license server available";
		break;

	case exDiscReasonLicenseNoLicense:
		text = "No valid license available";
		break;

	case exDiscReasonLicenseErrClientMsg:
		text = "Invalid licensing message";
		break;

	case exDiscReasonLicenseHwidDoesntMatchLicense:
		text = "Hardware id doesn't match software license";
		break;

	case exDiscReasonLicenseErrClientLicense:
		text = "Client license error";
		break;

	case exDiscReasonLicenseCantFinishProtocol:
		text = "Network error during licensing protocol";
		break;

	case exDiscReasonLicenseClientEndedProtocol:
		text = "Licensing protocol was not completed";
		break;

	case exDiscReasonLicenseErrClientEncryption:
		text = "Incorrect client license enryption";
		break;

	case exDiscReasonLicenseCantUpgradeLicense:
		text = "Can't upgrade license";
		break;

	case exDiscReasonLicenseNoRemoteConnections:
		text = "The server is not licensed to accept remote connections";
		break;

	default:
		if (reason > 0x1000 && reason < 0x7fff) {
			text = "Internal protocol error";
		} else {
			text = "Unknown reason";
		}
	}
	fprintf(stderr, "disconnect: %s.\n", text);
}
